import {Euler, Vector3, Spherical, Vector2, MathUtils} from 'three';
import {TWEEN} from "three/examples/jsm/libs/tween.module.min.js";


class MoveDirectionControls {
  constructor(camera, domElement) {

    if (domElement === undefined) {
      console.warn('MoveDirectionControls: The second parameter "domElement" is now mandatory.');
      domElement = document.body;
    }

    this.domElement = domElement;
    this.domElement.style.touchAction = 'none'; // disable touch scroll
    this.domElement.setAttribute('tabindex', '0')

    // 上下极角
    this.minPolarAngle = MathUtils.degToRad(60); // radians
    this.maxPolarAngle = MathUtils.degToRad(120); // radians

    //  左右极角
    this.minAzimuthAngle = -Infinity; // radians
    this.maxAzimuthAngle = Infinity; // radians

    // 开启旋转，旋转速度
    this.enableRotate = true;
    this.rotateSpeed = .001;

    // 开启阻力
    this.enableDamping = true;
    this.dampingFactor = 0.05;
    // internals
    const scope = this;
    this.object = camera

    const pointer = new Vector2()

    const touchesStart = [new Vector2(), new Vector2()];
    const touchesSEnd = [new Vector2(), new Vector2()];
    let touchesDownFov = camera.fov
    let fovDelta = camera.fov
    let fovDeltaY = 0
    this.zoomSpeed = 1
    this.minFov = 30
    this.maxFov = 75

    let needToSlide = false

    const rotateStart = new Vector2()
    const rotateDelta = new Vector2();
    const euler = new Euler(0, 0, 0, 'YXZ');

    let spherical = new Spherical();

    let PI_2 = Math.PI / 2;

    let vec = new Vector3();

    let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
    let forward = 0, right = 0;
    this.movingSpeed = .1;

    this.startFun = undefined
    this.moveFun = undefined
    this.endFun = undefined

    function onDown(e) {
      setPointer(e)
      if (scope.startFun) scope.startFun(e)

      if (scope.enableRotate) {
        rotateStart.x = e.pageX || e.touches[0].pageX
        rotateStart.y = e.pageY || e.touches[0].pageY
      }
      if (e.touches && e.touches.length === 2) {
        touchesDownFov = scope.object.fov
        touchesStart[0].set(e.touches[0].pageX, e.touches[0].pageY)
        touchesStart[1].set(e.touches[1].pageX, e.touches[1].pageY)
      }
      needToSlide = true
    }

    function onMove(e) {
      setPointer(e)

      if (scope.moveFun) scope.moveFun(e)

      // rotate
      if (scope.enableRotate && needToSlide &&
        (e.pageX || (e.touches && e.touches.length === 1))) {

        const pageX = e.pageX || e.touches[0].pageX,
          pageY = e.pageY || e.touches[0].pageY
        euler.setFromQuaternion(camera.quaternion);

        euler.y -= rotateDelta.x = (rotateStart.x - pageX) * scope.rotateSpeed
        euler.x -= rotateDelta.y = (rotateStart.y - pageY) * scope.rotateSpeed

        euler.y = Math.min(scope.maxAzimuthAngle, Math.max(scope.minAzimuthAngle, euler.y));
        euler.x = Math.max(PI_2 - scope.maxPolarAngle, Math.min(PI_2 - scope.minPolarAngle, euler.x));

        camera.rotation.copy(euler);

        rotateStart.x = pageX
        rotateStart.y = pageY

      }

      // fov
      if (e.touches && e.touches.length === 2) {
        touchesSEnd[0].set(e.touches[0].pageX, e.changedTouches[0].pageY)
        touchesSEnd[1].set(e.touches[1].pageX, e.changedTouches[1].pageY)
        const length = touchesSEnd[1].clone().sub(touchesSEnd[0]).length() -
          touchesStart[1].clone().sub(touchesStart[0]).length();
        fovDelta = Math.min(scope.maxFov, Math.max(scope.minFov, touchesDownFov - length * scope.zoomSpeed / 10));
        if (fovDelta !== scope.object.fov) fovDeltaY = fovDelta > scope.object.fov ? 1 : -1 // 这个用手机缩放会有重感
        scope.object.fov = fovDelta
        scope.object.updateProjectionMatrix()
      }

    }

    function onUp(e) {
      setPointer(e)
      if (scope.endFun) scope.endFun(e)

      needToSlide = false
    }

    function setPointer(e) {
      if (e.type.indexOf('mouse') > -1) {
        pointer.x = (e.pageX / canvas.offsetWidth) * 2 - 1;
        pointer.y = -(e.pageY / canvas.offsetHeight) * 2 + 1;
      } else if (e.type === 'touchend') {
        pointer.x = (e.changedTouches[0].pageX / canvas.offsetWidth) * 2 - 1;
        pointer.y = -(e.changedTouches[0].pageY / canvas.offsetHeight) * 2 + 1;
      } else {
        pointer.x = (e.touches[0].pageX / canvas.offsetWidth) * 2 - 1;
        pointer.y = -(e.touches[0].pageY / canvas.offsetHeight) * 2 + 1;
      }
    }

    this.getPointer = function() {
      return pointer
    }

    function getZoomScale() {
      return Math.pow(0.95, scope.zoomSpeed);
    }

    function onMouseWheel(e) {
      e.preventDefault();
      if (e.deltaY < 0) {
        fovDelta *= getZoomScale();
        fovDeltaY = -1
      } else if (e.deltaY > 0) {
        fovDelta /= getZoomScale()
        fovDeltaY = 1
      }
      fovDelta = Math.min(scope.maxFov, Math.max(scope.minFov, fovDelta));
      scope.object.fov = fovDelta
      scope.object.updateProjectionMatrix()
    }

    function onContextMenu(event) {
      event.preventDefault();
    }

    this.connect = function() {
      scope.domElement.addEventListener('keydown', onKeyDown, false)
      scope.domElement.addEventListener('keyup', onKeyUp, false)

      scope.domElement.addEventListener('contextmenu', onContextMenu);

      scope.domElement.addEventListener('touchstart', onDown, false)
      scope.domElement.addEventListener('touchmove', onMove, false)
      scope.domElement.addEventListener('touchend', onUp, false)

      scope.domElement.addEventListener('mousedown', onDown, false)
      scope.domElement.addEventListener('mousemove', onMove, false)
      scope.domElement.addEventListener('mouseup', onUp, false)

      scope.domElement.addEventListener('wheel', onMouseWheel, {passive: false});

      scope.domElement.addEventListener('mouseout', () => needToSlide = false, false)
    }

    this.dispose = function() {
      scope.domElement.removeEventListener('touchstart', onDown);
      scope.domElement.removeEventListener('touchmove', onMove);
      scope.domElement.removeEventListener('touchend', onUp)

      scope.domElement.removeEventListener('keydown', onKeyDown)
      scope.domElement.removeEventListener('keyup', onKeyUp)

      scope.domElement.removeEventListener('mousedown', onDown);
      scope.domElement.removeEventListener('mousemove', onMove);
      scope.domElement.removeEventListener('mouseup', onUp)

    };

    function onKeyDown(event) {
      // event.preventDefault()
      switch (event.keyCode) {
        case 87:
        case 38:
          moveForward = true;
          break;

        case 65:
        case 37:
          moveLeft = true;
          break;

        case 83:
        case 40:
          moveBackward = true;
          break;

        case 68:
        case 39:
          moveRight = true;
          break;
      }
    };

    function onKeyUp(event) {
      // event.preventDefault()
      switch (event.keyCode) {

        case 87:
        case 38:
          moveForward = false;
          break;

        case 65:
        case 37:
          moveLeft = false;
          break;

        case 83:
        case 40:
          moveBackward = false;
          break;

        case 68:
        case 39:
          moveRight = false;
          break;
      }

    };

    this.moveForward = function(distance) {
      vec.setFromMatrixColumn(camera.matrix, 0);
      vec.crossVectors(camera.up, vec);
      camera.position.addScaledVector(vec, distance * scope.movingSpeed);
    };

    this.moveRight = function(distance) {
      vec.setFromMatrixColumn(camera.matrix, 0);
      camera.position.addScaledVector(vec, distance * scope.movingSpeed);
    };

    this.getPosition = function() {
      return camera.position
    }

    this.getObject = function() {
      return camera;
    };

    this.getDirection = function() {
      spherical.theta = euler.y
      spherical.phi = euler.x
      return spherical
    };

    this.fromMoveTo = function({to, time, linear, mode,}) {
      new TWEEN.Tween(scope.object.position).to(to, time || 1000)
        .easing(TWEEN.Easing[linear || 'Sinusoidal'][mode || 'InOut']).start();
    }

    this.update = function() {

      forward = moveForward ? 1 : moveBackward ? -1 : 0;
      right = moveRight ? 1 : moveLeft ? -1 : 0;
      if (forward) scope.moveForward(forward);
      if (right) scope.moveRight(right);

      if (scope.enableDamping) {
        // rotate
        euler.y -= rotateDelta.x *= (1 - scope.dampingFactor)
        euler.x -= rotateDelta.y *= (1 - scope.dampingFactor)

        euler.y = Math.min(scope.maxAzimuthAngle, Math.max(scope.minAzimuthAngle, euler.y));
        euler.x = Math.max(PI_2 - scope.maxPolarAngle, Math.min(PI_2 - scope.minPolarAngle, euler.x));

        camera.rotation.copy(euler);

        // fov
        if (!needToSlide) {
          fovDeltaY *= getZoomScale() - scope.dampingFactor
          scope.object.fov = Math.min(scope.maxFov, Math.max(scope.minFov, fovDelta += fovDeltaY));
          scope.object.updateProjectionMatrix()
        }
      }

      TWEEN.update()
    }
    this.connect()

  }

}

export {MoveDirectionControls};
